登录注册页的视觉设计通常包含一张全屏或半屏的背景图片,表单在页面中的位置需要根据屏幕宽度动态调整。本节实现一个支持 left、right、center 三种布局位置的登录页,通过路由 meta 传递背景图片和表单位置参数,使用 JavaScript 动态计算表单的边距和样式,实现完整的响应式布局方案。
通过路由 meta 传递布局参数
SinglePage 布局本身是一个通用结构,不适合直接接收 props。但登录页需要告知布局组件表单应该显示在哪个位置、是否需要背景图片。解决方案是通过路由的 meta 字段传递这些信息:
// router/routes.ts
{
path: '/login',
name: 'Login',
component: () => import('~/pages/login/index.vue'),
meta: {
layout: 'SinglePage',
hideMenu: true,
position: 'left', // 表单位置:left | right | center
backgroundImage: 'https://images.pexels.com/photos/.../photo.jpeg',
},
}
typescript
在 SinglePage 布局中通过 useRoute() 获取这些参数:
// layouts/SinglePage.vue
const route = useRoute()
const bgUrl = ref('')
onBeforeMount(() => {
if (route.meta?.backgroundImage) {
bgUrl.value = route.meta.backgroundImage as string
}
})
typescript
背景图片容器
背景图片使用 position: absolute 脱离文档流,这样可以方便地控制其尺寸,且不影响表单区域的布局:
<!-- layouts/SinglePage.vue -->
<template>
<div class="h-screen relative overflow-hidden">
<!-- 背景图片容器 -->
<div
v-if="bgUrl"
class="absolute top-0 bg-cover bg-center"
:class="[
position === 'left' ? 'left-0' : 'right-0',
'lg:w-1/3 xl:w-1/2'
]"
:style="{ backgroundImage: `url(${bgUrl})`, height: '100%' }"
/>
<!-- 表单区域 -->
<div
class="relative z-10 h-full flex items-center"
:class="formContainerClass"
>
<router-view />
</div>
</div>
</template>
vue
响应式宽度控制
背景图片在不同屏幕宽度下使用不同的占比:
| 屏幕宽度 | 背景图片宽度 | UnoCSS class |
|---|---|---|
| < 1024px | 隐藏或全屏 | w-full |
| 1024px ~ 1440px | 1/3 | lg:w-1/3 |
| > 1440px | 1/2 | xl:w-1/2 |
当背景图片消失(小于 1024px)时,表单居中显示。
表单位置的动态计算
表单在页面中的位置取决于 position 配置和是否有背景图片。使用 JavaScript 动态计算表单的 margin-left 或 margin-right,使其在有背景图片时位于非图片区域居中:
// layouts/SinglePage.vue
const formContainerClass = computed(() => {
const position = route.meta?.position as string
const hasBg = !!bgUrl.value
if (!hasBg || position === 'center') {
return 'justify-center'
}
if (position === 'left') {
// 背景图在左侧,表单在右侧
return 'lg:justify-end xl:justify-end'
}
if (position === 'right') {
// 背景图在右侧,表单在左侧
return 'lg:justify-start xl:justify-start'
}
return 'justify-center'
})
typescript
表单样式随布局变化
表单的视觉样式也需要根据布局位置调整:
const formClass = computed(() => {
const position = route.meta?.position as string
const hasBg = !!bgUrl.value
// 有背景图时,表单需要白色背景和阴影以区分
if (hasBg) {
return [
'bg-white dark:bg-gray-800',
'lt-xl:shadow-lg lt-xl:rounded-lg lt-xl:p-8',
]
}
// 无背景图时,居中显示,不需要额外装饰
return 'p-8'
})
typescript
自定义 UnoCSS 断点
UnoCSS 默认不包含 1444px 的断点。如果需要精确控制某个特定宽度下的样式变化,可以在 UnoCSS 配置中添加自定义断点:
// uno.config.ts
import { defineConfig, presetUno, presetAttributify } from 'unocss'
export default defineConfig({
presets: [
presetUno(),
presetAttributify(),
],
theme: {
breakpoints: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1444px', // 自定义断点
},
},
})
typescript
添加后即可使用 lt-2xl:shadow-lg 这样的 class,表示当屏幕宽度小于 1444px 时显示阴影。
三种布局效果
通过修改路由 meta 中的 position 值,实现三种不同的布局效果:
// position: 'left' - 背景图在左侧,表单在右侧
meta: { position: 'left', backgroundImage: '...' }
// position: 'right' - 背景图在右侧,表单在左侧
meta: { position: 'right', backgroundImage: '...' }
// position: 'center' - 无背景图,表单居中
meta: { position: 'center' }
typescript
响应式行为总结
| 屏幕宽度 | position=left | position=right | position=center |
|---|---|---|---|
| < 1024px | 背景全屏,表单居中 | 背景全屏,表单居中 | 表单居中 |
| 1024px ~ 1440px | 背景占 1/3,表单在右侧 | 背景占 1/3,表单在左侧 | 表单居中 |
| > 1440px | 背景占 1/2,表单在右侧 | 背景占 1/2,表单在左侧 | 表单居中 |
为什么使用 JS 而非纯 CSS Flex 布局
虽然纯 CSS 的 Flex 布局也能实现类似效果,但它需要根据 position 的 left 和 right 值在不同位置放置两个背景图片 DOM 元素(一个在表单前面,一个在后面),通过 v-if 条件渲染来控制显示哪一个。相比之下,使用一个背景图片 DOM 元素配合 JavaScript 动态计算位置,代码更简洁、DOM 结构更清晰。
总结
登录页的响应式布局核心是通过路由 meta 传递布局参数(背景图片 URL、表单位置),在 SinglePage 布局中统一处理。背景图片使用 position: absolute 脱离文档流,表单通过 JavaScript 动态计算边距和样式类。UnoCSS 的自定义断点可以精确控制特定宽度下的样式变化,实现 left、right、center 三种布局模式的无缝切换。
↑